﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Runtime.InteropServices;
using System.IO;

namespace WindowsFormsApplication1
{


    public partial class MainForm : Form
    {

        private const string FFACE_LIBRARY = "FFACE.dll";

        #region FFACE Imports
        [DllImport(FFACE_LIBRARY, EntryPoint = "CreateInstance")]
        public static extern int CreateInstance(UInt32 PID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern void DeleteInstance(int instance);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetSafeItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetSackItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetLockerItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetSatchelItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetInventoryItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern INVENTORYITEM GetStorageItem(int instanceID, byte index);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetInventoryMax(int instanceID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetSafeMax(int instanceID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetSackMax(int instanceID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetSatchelMax(int instanceID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetLockerMax(int instanceID);
        [DllImport(FFACE_LIBRARY, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        public static extern byte GetStorageMax(int instanceID);

        #endregion

        #region FFACE Structures
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct INVENTORYITEM
        {
            public ushort ID;
            public byte Index;
            public uint Count;
            public uint Flag;
            public uint Price;
            public ushort Extra;
        }
        #endregion

        private int ffaceHandle;
        ListViewColumnSorter lvwColSort = new ListViewColumnSorter();

        Dictionary<int, ItemData> itemDict;

        Dictionary<String, List<String>> spellcastDict;

        /* Inventory structure:
         * {location: [itemNames]}
         */
        Dictionary<String, List<String>> inventoryDict;

        public MainForm()
        {
#if DEBUG
            TextWriterTraceListener tr = new TextWriterTraceListener(System.IO.File.CreateText("debug.txt"));
            Debug.Listeners.Add(tr);
#endif
            InitializeComponent();
            itemDict = new Dictionary<int, ItemData>();
            ffaceHandle = -1;

            spellcastDict = new Dictionary<string, List<string>>();
            inventoryDict = new Dictionary<string, List<string>>();

#if DEBUG
            Debug.WriteLine("Beginning parsing of resources");
            Debug.Flush();
#endif
            XmlTextReader reader = new XmlTextReader("Resources/armor.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/general.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/usable.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/weapons.xml");
            parseItemList(reader);

#if DEBUG
            Debug.WriteLine("End parsing of resources");

            Debug.WriteLine("itemDict contains " + itemDict.Keys.Count + " entries");

            Debug.Flush();
#endif
            lvwColSort.Order = SortOrder.Ascending;
            itemList.ListViewItemSorter = lvwColSort;

            itemList.Sort();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void parseSpellcast(XmlTextReader reader, String fileName)
        {
#if DEBUG
            Debug.WriteLine("Attempting to parse file: "+fileName);
            Debug.Flush();
#endif
            bool isItem = false;

            List<String> itemNames = new List<string>();

            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element: // The node is an element.
                        switch(reader.Name.ToLower()){
                            case "item":
                            case "main":
                            case "sub":
                            case "range":
                            case "ammo":
                            case "head":
                            case "neck":
                            case "lear":
                            case "rear":
                            case "body":
                            case "hands":
                            case "lring":
                            case "rring":
                            case "back":
                            case "waist":
                            case "legs":
                            case "feet":
                                isItem = true;
                                break;
                        }
                        break;
                    case XmlNodeType.Text: //Display the text in each element.
                        if (!isItem)
                            break;
                        //process some crap here
                        
                        itemNames.Add(reader.Value);
                        break;
                    case XmlNodeType.EndElement: //Display the end of the element.
                        isItem = false;
                        break;
                }
            }
#if DEBUG
            Debug.WriteLine("Added "+itemNames.Count+" items");
            Debug.Flush();
#endif
            spellcastDict.Add(fileName, itemNames);
#if DEBUG
            Debug.WriteLine("Added to spellcastDict, now contains "+spellcastDict.Keys.Count()+" entries");
            Debug.Flush();
#endif
        }


        private void parseItemList(XmlTextReader reader)
        {
            bool isId = false;
            bool isStack = false;
            bool isFlags = false;
            bool isGraphic = false;
            bool isName = false;

            int id = 0;
            int stack = 0;
            int flags = 0;
            string name = "";
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element: // The node is an element.
                        if (reader.Name.Equals("field"))
                        {
                            // Read the attributes.
                            while (reader.MoveToNextAttribute())
                            {
                                if (reader.Name.Equals("name"))
                                {
                                    if (reader.Value.Equals("id"))
                                    {
                                        isId = true;
                                        isStack = false;
                                        isFlags = false;
                                        isName = false;
                                    }
                                    else if (reader.Value.Equals("flags"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = true;
                                        isName = false;
                                    }
                                    else if (reader.Value.Equals("stack-size"))
                                    {
                                        isId = false;
                                        isStack = true;
                                        isFlags = false;
                                        isName = false;
                                    }
                                    else if (reader.Value.Equals("name"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = false;
                                        isName = true;
                                    }

                                }
                            }
                        }
                        else if (reader.Name.Equals("thing"))
                        {
                            while (reader.MoveToNextAttribute())
                            {
                                if (reader.Name.Equals("type"))
                                {
                                    if (reader.Value.Equals("Graphic"))
                                    {

                                        isGraphic = true;
                                    }
                                }
                            }
                        }
                        break;
                    case XmlNodeType.Text: //Display the text in each element.
                        if (isGraphic)
                            break;
                        if (isId)
                        {
                            id = Int32.Parse(reader.Value);
                        }
                        else if (isStack)
                        {
                            stack = Int32.Parse(reader.Value);
                        }
                        else if (isFlags)
                        {
                            flags = Int32.Parse(reader.Value, System.Globalization.NumberStyles.HexNumber);
                        }
                        else if (isName)
                        {
                            name = reader.Value;
                        }
                        break;
                    case XmlNodeType.EndElement: //Display the end of the element.
                        if (reader.Name.Equals("thing"))
                        {
                            itemDict[id] = new ItemData();
                            itemDict[id].flags = flags;
                            itemDict[id].stackSize = stack;
                            itemDict[id].name = name;
                            isGraphic = false;
                        }
                        else if (reader.Name.Equals("field"))
                        {
                            isId = false;
                            isStack = false;
                            isFlags = false;
                            isName = false;
                        }
                        break;
                }
            }
        }

        private void spellcastBox_SelectedIndexChanged(object sender, EventArgs e)
        {
#if DEBUG
            Debug.WriteLine("Spellcast indexChanged event starting");
            Debug.Flush();
#endif
            ListBox lb = (ListBox)sender;

            setupProgressLabel("Filtering...");

            List<String> combinedItems = new List<String>();

            foreach (string s in lb.SelectedItems)
            {
#if DEBUG
                Debug.WriteLine("Adding item from "+s);
                Debug.Flush();
#endif
                combinedItems.AddRange(spellcastDict[s]);
#if DEBUG
                Debug.WriteLine("combinedItems now contains "+combinedItems.Count+" values");
                Debug.Flush();
#endif
            }

            //filter out gear that is not in any of the given xmls
            Dictionary<String, List<String>> filteredItems = new Dictionary<string, List<string>>();



            foreach(String loc in new String[]{"Inventory", "Sack", "Satchel", "Safe", "Locker", "Storage"}){
#if DEBUG
                Debug.WriteLine("Adding items from "+loc);
                Debug.Flush();
#endif
                //deep copy the list first
                List<String> originalItemNames = new List<string>();
                foreach (String str in inventoryDict[loc])
                {
                    originalItemNames.Add((String)str.Clone());
                }
                filteredItems[loc] = originalItemNames;


                List<String> removes = new List<String>();

                foreach (String itemName in filteredItems[loc])
                {
                    if (combinedItems.Contains(itemName, StringComparer.OrdinalIgnoreCase))
                    {
                        removes.Add(itemName);
                    }
                }

                foreach (String remove in removes)
                {
                    filteredItems[loc].Remove(remove);
                }
            }

            //clear the listbox, add new items
            itemList.Items.Clear();

            foreach (String key in filteredItems.Keys)
            {
#if DEBUG
                Debug.WriteLine("Adding to listbox from "+key);
                Debug.Flush();
#endif
                foreach (String itemName in filteredItems[key])
                {
                    ListViewItem lvi = new ListViewItem(itemName);
                    lvi.SubItems.Add(key);

                    itemList.Items.Add(lvi);
                }
            }

            itemList.Sort();

            setupProgressLabel("Choose Filters");
        }

        private void scanButton_Click(object sender, EventArgs e)
        {
#if DEBUG
            Debug.WriteLine("Starting scanButton Clicked event");
            Debug.Flush();
#endif
            detachFFACE();


            //get list of ffxi processes, populate list
            Console.WriteLine("Scanning Processes");

            Process[] procs = System.Diagnostics.Process.GetProcessesByName("pol");

            characterBox.Items.Clear();

            characterBox.Items.Add("None");

            for (int i = 0; i < procs.Length; i++)
            {
                Console.WriteLine(procs[i].MainWindowTitle);
                if (procs[i].MainWindowTitle.StartsWith("Final Fantasy") ||
                    procs[i].MainWindowTitle.StartsWith("PlayOnline"))
                {
                    //ignore
                    continue;
                }
#if DEBUG
                Debug.WriteLine("Found character: "+procs[i].MainWindowTitle);
                Debug.Flush();
#endif
                characterBox.Items.Add(procs[i].MainWindowTitle);

            }

            characterBox.Text = "None";
            characterBox.SelectedIndex = 0;

            progressLabel.Text = "Choose a character or rescan";
        }

        private void detachFFACE()
        {
#if DEBUG
            Debug.WriteLine("Detaching FFACE");
            Debug.Flush();
#endif
            if (ffaceHandle == -1)
                return;

            DeleteInstance(ffaceHandle);
            ffaceHandle = -1;

            itemList.Items.Clear();
            spellcastBox.Items.Clear();

        }

        private void characterBox_SelectionChangeCommitted(object sender, EventArgs e)
        {
            ComboBox senderBox = (ComboBox)sender;

            String selectedChar = senderBox.SelectedItem.ToString();

            if (selectedChar.Equals("None"))
            {
                detachFFACE();
                return;
            }

            String windowerDirectory = "";

            Console.WriteLine("Attaching to " + selectedChar);

            Process[] procs = System.Diagnostics.Process.GetProcessesByName("pol");

            for (int i = 0; i < procs.Length; i++)
            {
                if (selectedChar.Equals(procs[i].MainWindowTitle))
                {

                    ffaceHandle = CreateInstance((UInt32)(procs[i].Id));

                    //locate the windower directory too
                    ProcessModuleCollection modules = procs[i].Modules;
                    foreach (ProcessModule module in modules)
                    {
                        if (module.ModuleName.ToLower().Equals("hook.dll"))
                        {

                            windowerDirectory = module.FileName.Substring(0, module.FileName.ToLower().IndexOf("hook.dll"));
#if DEBUG
                            Debug.WriteLine("Found window directory: "+windowerDirectory);
                            Debug.Flush();
#endif
                        }
                    }
                }

            }

            if (ffaceHandle == -1)
            {
#if DEBUG
                Debug.WriteLine("FFACE Handle set to -1");
                Debug.Flush();
#endif

                //odd...maybe the process exited inbetween scan and selection.  return.
                return;
            }

            loadDataDelegate loadDelegate = new loadDataDelegate(loadData);

            loadDelegate.BeginInvoke(windowerDirectory + "plugins\\spellcast\\", selectedChar, null, null);

        }

        private delegate void loadDataDelegate(String directoryPath, String charName);

        private void loadData(String directoryPath, String charName)
        {
#if DEBUG
            Debug.WriteLine("loadData called");
            Debug.Flush();
#endif
            clearSpellcastBox();
            spellcastDict = new Dictionary<string, List<string>>();
            loadXMLList(directoryPath, "");
            loadItemList();
            setupProgressLabel("Choose Filters");
        }

        delegate void voidCallback();

        private void clearSpellcastBox()
        {
            if (this.spellcastBox.InvokeRequired)
            {
                voidCallback c = new voidCallback(clearSpellcastBox);
                this.Invoke(c, null);
            }
            else
            {
                this.spellcastBox.Items.Clear();
            }
        }

        delegate void stringDelegate(String text);

        void setupProgressLabel(String text)
        {
            if (this.progressLabel.InvokeRequired)
            {
                stringDelegate c = new stringDelegate(setupProgressLabel);
                this.Invoke(c, new object[] { text });
            }
            else
            {
                this.progressLabel.Text = text;
            }
        }

        private void addSpellcastItem(String item)
        {

            if (this.spellcastBox.InvokeRequired)
            {
                stringDelegate c = new stringDelegate(addSpellcastItem);
                this.Invoke(c, new object[] { item });
            }
            else
            {
                spellcastBox.Items.Add(item);
            }
        }

        private void loadXMLList(String directoryPath, String prefix)
        {
#if DEBUG
            Debug.WriteLine("Loading XML from "+directoryPath);
            Debug.Flush();
#endif
            DirectoryInfo di = new DirectoryInfo(directoryPath);

            FileInfo[] spellcastFiles = di.GetFiles();

            setupProgressLabel("Scanning spellcast XML files");

            for (int i = 0; i < spellcastFiles.Length; i++)
            {
                FileInfo scFile = spellcastFiles[i];

#if DEBUG
                Debug.WriteLine("Processing file "+scFile.Name);
                Debug.Flush();
#endif

                if (scFile.Name.ToLower().EndsWith("xml"))
                {
                    try
                    {
                        parseSpellcast(new XmlTextReader(scFile.OpenRead()), prefix + scFile.Name);
                        addSpellcastItem(prefix + scFile.Name);
                    }
                    catch (XmlException ex)
                    {
#if DEBUG
                        Debug.WriteLine("XmlException encountered.");
                        Debug.WriteLine(ex.Message);
                        Debug.WriteLine(ex.StackTrace);
                        Debug.Flush();
#endif
                        MessageBox.Show("Error parsing " + scFile.Name + ":\n" + ex.Message);
                    }

                }
            }

            foreach(DirectoryInfo di2 in di.GetDirectories()){
#if DEBUG
                Debug.WriteLine("Processing subdirectory "+di2.FullName);
                Debug.Flush();
#endif
                loadXMLList(di2.FullName, prefix+di2.Name + "\\");
            }

        }

        private void sortItemList()
        {
            if (this.itemList.InvokeRequired)
            {
                voidCallback c = new voidCallback(sortItemList);
                this.Invoke(c, null);
            }
            else
            {
                itemList.Sort();
            }
        }


        private void loadInventoryList()
        {
            if (this.itemList.InvokeRequired)
            {
                voidCallback c = new voidCallback(loadInventoryList);
                this.Invoke(c, null);
            }
            else
            {
                foreach (String key in inventoryDict.Keys)
                {
                    foreach (String itemName in inventoryDict[key])
                    {
                        ListViewItem lvi = new ListViewItem(itemName);
                        lvi.SubItems.Add(key);

                        itemList.Items.Add(lvi);
                    }
                }
            }
            sortItemList();
        }

        private void loadItemList()
        {
            setupProgressLabel("Scanning inventory");
            inventoryDict = new Dictionary<string, List<string>>();
            //looks like fface indexes item lists as 1-indexed, so i=1 <= max
            int max = GetInventoryMax(ffaceHandle);
            List<String> itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Inventory; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetInventoryItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Inventory"] = itemList;



            max = GetSackMax(ffaceHandle);
            itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Sack; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetSackItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Sack"] = itemList;


            max = GetSatchelMax(ffaceHandle);
            itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Satchel; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetSatchelItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Satchel"] = itemList;



            max = GetSafeMax(ffaceHandle);
            itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Safe; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetSafeItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Safe"] = itemList;


            max = GetLockerMax(ffaceHandle);
            itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Locker; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetLockerItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Locker"] = itemList;


            max = GetStorageMax(ffaceHandle);
            itemList = new List<String>();

#if DEBUG
            Debug.WriteLine("Processing Storage; max = " + max);
            Debug.Flush();
#endif

            for (int i = 1; i <= max; i++)
            {
                INVENTORYITEM invItem = GetStorageItem(ffaceHandle, (byte)i);
                if (invItem.ID > 0 && invItem.ID < 65535)
                {
                    //lookup the item name
                    try
                    {
                        itemList.Add(itemDict[invItem.ID].name);
                    }
                    catch (KeyNotFoundException) {
#if DEBUG
                        Debug.WriteLine("(loadItemList) KeyNotFoundException on key " + invItem.ID);
                        Debug.Flush();
#endif
                    }
                }
            }

            inventoryDict["Storage"] = itemList;

#if DEBUG
            Debug.WriteLine("Processed all inventory items");
            Debug.Flush();
#endif

            loadInventoryList();

        }

        private void itemList_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            Console.WriteLine(e.Column);

            lvwColSort.SortColumn = e.Column;
            if (lvwColSort.Order.Equals(SortOrder.Ascending))
            {
                lvwColSort.Order = SortOrder.Descending;
            }
            else
            {
                lvwColSort.Order = SortOrder.Ascending;
            }

            itemList.Sort();

        }

    }


    public class ItemData
    {
        public int flags;
        public int stackSize;
        public string name;
    }
}
